﻿#pragma once

#include <learn/dll_ext.h>
#include <learn/geom/vec.h>

#include <iostream>
#include <string>

namespace xi
{

namespace painter
{

using namespace xi::geom;

class EXT_SYMBOL Shader
{
    struct Uniform
    {
      private:
        struct _Impl
        {
            std::map<std::string, int> intData;
            std::map<std::string, float> floatData;
            std::map<std::string, Vec2f> vec2Data;
            std::map<std::string, Vec3f> vec3Data;
            std::map<std::string, Vec4f> vec4Data;
            std::map<std::string, Mat4x4<float>> mat4Data;

            _Impl() = default;
            _Impl(_Impl &&) = delete;
        };

        std::shared_ptr<_Impl> m_impl;

      public:
        Uniform() : m_impl(std::make_shared<_Impl>())
        {
        }

        _Impl *operator->() const noexcept
        {
            return m_impl.get();
        }
    };

  protected:
    unsigned int m_ID;
    std::function<Vec4f(int index)> m_vertexShader;
    std::function<Vec4f(void)> m_fragmentShader;
    Uniform m_uniform;

    // 绘图数据
    VAO m_VAO;
    VBO m_VBO;
    EBO m_EBO;

    /**
     * @brief ID 生成器
     *
     * @return std::size_t
     */
    static std::size_t _gen_id() noexcept;

  public:
    /**
     * @brief Construct a new Shader object
     *
     * @param[in] vertexShader
     * @param[in] fragmentShader
     */
    Shader(std::function<Vec4f(int index)> vertexShader, std::function<Vec4f(void)> fragmentShader);

    /**
     * @brief 传入 uniform 变量
     *
     * @tparam T
     * @param[in] var
     * @param[in] val
     */
    template <typename T> void set_value(const char *var, T val)
    {
        // 注意这里类型判断为整数
        if constexpr (std::is_integral_v<T>)
            m_uniform->intData[var] = val;
        else if constexpr (std::is_same_v<T, float>)
            m_uniform->floatData[var] = val;
        else if constexpr (std::is_same_v<T, Vec2f>)
            m_uniform->vec2Data[var] = val;
        else if constexpr (std::is_same_v<T, Vec3f>)
            m_uniform->vec3Data[var] = val;
        else if constexpr (std::is_same_v<T, Vec4f>)
            m_uniform->vec4Data[var] = val;
        else if constexpr (std::is_same_v<T, Mat4x4<float>>)
            m_uniform->mat4Data[var] = val;
        else
            throw std::runtime_error("Unsupported type!");
    }

    /**
     * @brief 绘制三角形
     *
     * @param[in] k
     */
    void draw_primitive(int k);

    /**
     * @brief 绘制物体
     *
     * @param[in] count
     * @param[in] element
     */
    void draw_elements(int count);
};

} // namespace painter

} // namespace xi
